package P3;


import Graphing.GnutellaGraph;
import NIOCommunication.NIOProcessor;
import NIOCommunication.NIOProcessorCloseAllCommand;
import NIOCommunication.NIOProcessorCloseContextCommand;
import NIOCommunication.NIOServerSocketContext;
import java.io.File;
import java.io.FileWriter;
import java.util.Iterator;
import java.util.Vector;

public class Coordinator extends Thread
{
    public enum State
    {
        STATE_NEW,
        STATE_WAIT_FOR_WORKERS,
        STATE_WORKERS_CRAWLING,
        STATE_GATHERING_RESULTS,
        STATE_ALL_RESULTS_IN,
        STATE_PROCESSING,
    }
    
    public Vector<WorkerInterface> mWorkers = new Vector<WorkerInterface>();
    public Vector<CrawlResult> mResults = new Vector<CrawlResult>();
    public NIOProcessor mProcessor;
    public CoordinatorServerContext mServer;
    public int mNumResults = 0;
    public State mState;
    public String mBootstrapNode = "reala.ece.ubc.ca:5627";
    public GnutellaGraph mGraph = null;
    public int mMaxWorkers = 0;
    
    public Coordinator()
    {
    	mProcessor = new NIOProcessor();
        mState = State.STATE_NEW;
    }
    
    public void OnNewResult(CrawlResult result)
    {
        if(mState != State.STATE_GATHERING_RESULTS)
        {
            System.out.println("Received premature result.");
            return;
        }
        
        ++mNumResults;
        mGraph.ProcessResult(result);
        //mResults.add(result);
        System.out.println("We've received " + mNumResults + " results.");
        synchronized(ticks)
        {
            ticks = 0;
        }
    }
    
    public void OnNewWorkerConnection(WorkerInterface worker)
    {
        mWorkers.add(worker);
        worker.SendMessage("Hello worker!");
        //worker.SendMessage("Bootstrap");
        System.out.println("New worker connected.  We now have " + mWorkers.size() + " workers.");
        mMaxWorkers = Math.max(mMaxWorkers, mWorkers.size());
        if(mState == State.STATE_WORKERS_CRAWLING)
        {
                String bootstrapMessage = "Bootstrap\n" + mBootstrapNode;
                worker.SendMessage(bootstrapMessage);
        }
    }
    
    public void OpenWorkerConnections()
    {
        mState = State.STATE_WAIT_FOR_WORKERS;
        System.out.println("Waiting for workers to connect");
    	mServer = new CoordinatorServerContext(this, mProcessor);
        
        int WorkerConnectionWait = 10;
        for(int i = 0; i < WorkerConnectionWait; ++i)
        {
            try
            {
                System.out.println("Countdown until start: " + (WorkerConnectionWait - i) + " seconds");
                Thread.sleep(1000);
            }
            catch(Exception e)
            {
                e.printStackTrace();
            }
        }
        
        if(mWorkers.size() == 0)
        {
            System.out.println("No workers found... Quitting");
            System.exit(0);
        }
    }
    
        int CrawlWait = 60 * 30;
    public void BeginCrawling()
    {
        mState = State.STATE_WORKERS_CRAWLING;
        System.out.println("Beginning to crawl with " + mWorkers.size() + " workers.");
        
        synchronized(mWorkers)
        {
            Iterator<WorkerInterface> it = mWorkers.iterator();
            while(it.hasNext())
            {
                // TODO:  Give each worker a range?
                WorkerInterface worker = it.next();
                String bootstrapMessage = "Bootstrap\n" + mBootstrapNode;
                worker.SendMessage(bootstrapMessage);
            }
        }
        
        for(int i = 0; i < CrawlWait; ++i)
        {
            try
            {
                System.out.println("Countdown until finished crawling: " + (CrawlWait - i) + " seconds");
                Thread.sleep(1000);
            }
            catch(Exception e)
            {
                e.printStackTrace();
            }
        }
    }
    
    Integer ticks = new Integer(0);
    public void StartGatheringData()
    {
        mGraph = new GnutellaGraph(2048, 2048);
        mState = State.STATE_GATHERING_RESULTS;
        mProcessor.QueueCommand(new NIOProcessorCloseContextCommand(mServer));
        
        synchronized(mWorkers)
        {
            Iterator<WorkerInterface> it = mWorkers.iterator();
            while(it.hasNext())
            {
                WorkerInterface worker = it.next();
                worker.SendMessage("Send Data");
            }
        }
        
        //int ticks = 0;
        while(mState != State.STATE_ALL_RESULTS_IN)
        {
            try
            {
                synchronized(ticks)
                {
                    System.out.println("Waiting for all results to be returned. " + (++ticks));
                    if(ticks >= 10)
                    {
                        System.out.println("Giving up on waiting for results");
                        mState = State.STATE_ALL_RESULTS_IN;
                        break;
                    }
                }
                Thread.sleep(3000);
            }
            catch(Exception e)
            {
                e.printStackTrace();
            }    
        }
    }
    
    public void OnWorkerDisconnected(WorkerInterface worker)
    {
        mWorkers.remove(worker);
        System.out.println("Worker disconnected.  We now have " + mWorkers.size() + " workers.");
        
        if(mWorkers.size() == 0)
        {
            OnAllWorkersDisconnected();
        }
    }
    
    public void OnAllWorkersDisconnected()
    {
        System.out.println("All workers disconnected.");
        if(!(mState == State.STATE_GATHERING_RESULTS))
        {
            System.out.println("Quitting prematurely");
            System.exit(0);
        }
        else
        {
            mState = State.STATE_ALL_RESULTS_IN;
            // all results are in.
        }
    }
    
    public void ProcessResults()
    {
        mState = State.STATE_PROCESSING;
        
        try
        {
            Thread.sleep(3000);
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
        
        System.out.println("Creating image for " + mGraph.GetNumNodes() + " nodes");
        //mGraph.DrawToFile("Test");
        
        System.out.println("Creating the HTML");
    	mGraph.GenerateHTML("WWW", mMaxWorkers, CrawlWait);
        
        System.out.println("Done processing results!");
    }
    
    public void SaveResults()
    {
        try
        {
            FileWriter out = new FileWriter(new File("Results.log"));
        
            Iterator<CrawlResult> it = mResults.iterator();
            while(it.hasNext())
            {
                CrawlResult result = it.next();
                out.write(result + "\n");
            }
            
            out.close();
            System.out.println("Save completed");
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
    }
    
    @Override
    public void run()
    {
        OpenWorkerConnections();
        
        BeginCrawling();
        
        StartGatheringData();
        
        ProcessResults();
        
        System.out.println("All done!");
        
        System.exit(0);
        
    }
    
    public static void main(String[] args)
    {
        Coordinator coordinator = new Coordinator();
        coordinator.start();
    }
}
